; docformat = 'rst'
;
; NAME:
;       CDF_Info__Define
;
;*****************************************************************************************
;   Copyright (c) 2013, Matthew Argall                                                   ;
;   All rights reserved.                                                                 ;
;                                                                                        ;
;   Redistribution and use in source and binary forms, with or without modification,     ;
;   are permitted provided that the following conditions are met:                        ;
;                                                                                        ;
;       * Redistributions of source code must retain the above copyright notice,         ;
;         this list of conditions and the following disclaimer.                          ;
;       * Redistributions in binary form must reproduce the above copyright notice,      ;
;         this list of conditions and the following disclaimer in the documentation      ;
;         and/or other materials provided with the distribution.                         ;
;       * Neither the name of the <ORGANIZATION> nor the names of its contributors may   ;
;         be used to endorse or promote products derived from this software without      ;
;         specific prior written permission.                                             ;
;                                                                                        ;
;   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY  ;
;   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ;
;   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT  ;
;   SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       ;
;   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ;
;   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR   ;
;   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN     ;
;   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN   ;
;   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  ;
;   DAMAGE.                                                                              ;
;*****************************************************************************************
;
; PURPOSE:
;+
;       This is class for reading CDF files. The CDF file is opened and Global and
;       Variable attributes are automatically loaded. Data (and metadata) can be read 
;       and accessed through the class methods.
;
;       Information about CDF files can be found at the following site::
;           http://spdf.gsfc.nasa.gov/istp_guide/istp_guide.html
;           http://cdaweb.gsfc.nasa.gov/pub/software/cdf/dist/cdf34_1/idl/
;           http://cdf.gsfc.nasa.gov/
;           http://cdf.gsfc.nasa.gov/html/leapseconds.html
;
; :Categories:
;
;       CDF Utilities, File I/O
;
; :Examples:
;   Example 1: Open a file and display information about it::
;       filename = /path/to/file/filename.ext
;       myCDF = obj_new('CDF_Info', filename)
;       myCDF -> help
;       myCDF -> help, /VARIABLES
;       myCDF -> help, /ATTRIBUTES
;       myCDF -> help, /VARATTS
;
; :Author:
;   Matthew Argall::
;       University of New Hampshire
;       Morse Hall, Room 113
;       8 College Rd.
;       Durham, NH, 03824
;       matthew.argall@wildcats.unh.edu
;
; :History:
;   Modification History::
;
;       02/15/2013  -   Written by Matthew Argall (adapted from cdf__define)
;       03/01/2013  -   Added the ::check_zvariable and ::ensure_varType methods - MRA
;       03/02/2013  -   Added the ::ensure_attType method - MRA
;       03/05/2013  -   Added ::has_attribute and ::has_variable - MRA
;       03/21/2013  -   For consistency, variable and attribute existence checks are
;                           performed via ::ensure_attType and ::ensure_varType. - MRA
;       06/25/2013  -   Some CDF files do not place all global attributes before the
;                           variable attributes. Basic checks for ATTNUM > NGATTS fail
;                           in this case. Fixed for GET_VARATT, LOAD_VARATTS methods. - MRA
;       06/26/2013  -   Added ::VarHasAtt method to determine if a variable has a
;                           particular variable attribute. - MRA
;       06/27/2013  -   Added the ATT_VALUE keyword to ::VARHASATT to save an extra call
;                           to ::GET_VARATT. Added keyword DIALOG_PARENT to ::Open. - MRA
;       07/03/2013  -   Certain characters and words are not allowed to be structure tags.
;                           As such, tag names are not always the same as variable names.
;                           Now, variable names are now used everywhere, not structure
;                           tags. - MRA
;       08/08/2013  -   Began a major re-write. Separated the Load from Open, changed the
;                           way data is stored internally, Get_[Var/Att][Name/Num] methods
;                           are now stand-alone. Removed VarAtt methods and properties.
;                           Updated other methods to conform. - MRA
;       2013/12/30  -   NumGEntries=0 was causing errors when loading Global Attributes.
;                           Fixed. Added the Close method. - MRA
;       2014/01/19  -   The function MrIsA changed. Adjustments have been made. - MRA
;       2014/03/06  -   Renamed the Get_Property method to GetProperty. Inherit IDL_Object. - MRA
;-
;*****************************************************************************************
;+
;   The purpose of this method is to provide a means of closing the CDF file, if it
;   is open.
;-
pro CDF_Info::Close
    if self.cdf_id ne 0 then cdf_close, self.cdf_id
    self.cdf_id = 0
end

;+
;   The purose of this method is to print helpful information about the CDF file and
;   how to use CDF_Info.
;-
pro CDF_Info::Help, input, $
ATTRIBUTES = attributes, $
VARATTS = varatts, $
VARIABLES = variables
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif
    
    ;Check which keywords were set.
    specific_help = keyword_set(attributes) + keyword_set(variables) + keyword_set(varatts)
    
    ;if no input was given, then print general information
    if specific_help eq 0 then begin
        ;Give information about the attribute structure
        print, '----------------------------------------------------'
        print, '               ATTRIBUTE INFORMATION                '
        print, '----------------------------------------------------'
        print, 'There is a 1-to-1 correspondence between attname'
        print, 'elements and the elements of each attribute property'
        print, '(i.e. attname[3] is associated with attvalue[3], etc.).'
        print, ''
        print, 'For information about the attributes, use:'
        print, '          myObj -> help [,attname or attnum], /ATTRIBUTES'
        print, ''
        print, ''

        ;Give information about the variable structure.
        print, '----------------------------------------------------'
        print, '                VARIABLE INFORMATION                '
        print, '----------------------------------------------------'
        print, 'For more help, use:'
        print, '          myObj -> help, variable_name, /VARIABLE'
        print, 'where "variable_name" is the name of one of the CDF variables.'
        print, 'All variables share the same structure format.'
        print, ''
        print, 'For information about variables use:'
        print, '          myObj -> help [, variable], /VARIABLES [, ZVARIABLE=zvariable]'
        print, '          myObj -> help [, variable], /VARATTS [, ZVARIABLE=zvariable]'
        print, ''
        
        return
    endif
    
    ;Print specific information about attributes and variables
    if keyword_set(attributes) then self -> help_attributes, input
    if keyword_set(variables) then self -> help_variables, input, ZVARIABLE=zvariable
    if keyword_set(varatts) then self -> help_varatts, input, ZVARIABLE=zvariable
end


;+
; The purpose of this method is to print the variable attributes associated with
; each variable
;
;   Note::
;       Does not print variable attributes for r-variables (/ZVARIABLE is set).
;
; :Private:
;
; :Params:
;       VARIABLE:           in, optional, type=string/strarr/int/intarr
;                           CDF variable name(s) or number(s). If not given, the variable
;                               attributes for all variables will be printed.
;
; :Keywords:
;       ZVARIABLE:          in, out, optional, type=Boolean, default=0
;                           Indicate that `VARIABLE` is a zvariable. If `VARIABLE` is an
;                               array and ZVARIABLE is a scalar, then the same zvariable
;                               state is applied to all elements of `VARIABLE`. Otherwise,
;                               ZVARIABLE must have the same number of elements as `VARIABLE`
;
; :Uses:
;   Uses the following external programs::
;       type_to_format_code.pro
;
; :History:
;   Modification History::
;       03/01/2013  -   Added the VARIABLE parameter and ZVARIABLE keyword - MRA
;-
pro CDF_Info::Help_VarAtts, variable, $
ZVARIABLE = zvariable
    compile_opt idl2, hidden
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

;---------------------------------------------------------------------
;Check Inputs ////////////////////////////////////////////////////////
;---------------------------------------------------------------------
    
    ;Default to Z-Variables
    if n_elements(zvariable) eq 0 then zvariable = 1
    
    ;If VARIABLE was given, Ensure they are variable numbers
    if n_elements(variable) eq 0 $
        then varnum = self -> Get_VarNum(IS_ZVAR=zvariable) $
        else varnum = self -> Ensure_VarType(variable, /NUMBER, IS_ZVAR=zvariable)

;---------------------------------------------------------------------
;Walk Through the Variables //////////////////////////////////////////
;---------------------------------------------------------------------
    n_vars = n_elements(varnum)
    for i = 0, n_vars-1 do begin
        ;z-variables are stored after the r-variables, so if ZVARIABLE=1, then add
        ;NRVARS to VARNUM number to get the z-variable structure index
        iVar = varnum[i] + self.nrvars*zvariable[i]
    
        print, ''
        print, '-------------------------------------------------------------'
        print, 'VARIABLE NAME: ', self -> get_varname(iVar)
        print, '-------------------------------------------------------------'
        print, '-ATT NAME-', '-CDF TYPE-', '-ATT VALUE-', format='(a10, 15x, a10, 7x, a11)'

    ;---------------------------------------------------------------------
    ;Walk Through the Variables Attributes ///////////////////////////////
    ;---------------------------------------------------------------------
        nvatts = (*self.variables).(iVar).natts
        for iAtt = 0, nvatts-1 do begin
            ;get the type and number of elements of the att value
            idl_type = size((*self.variables).(iVar).attributes.(iAtt).value, /TYPE)
            n_values = n_elements((*self.variables).(iVar).attributes.(iAtt).value)
            
            ;Convert the type code to a format code character. Print any floats or doubles
            ;in exponential notation so that the print statement does not mess up the
            ;precision
            format_char = type_to_format_code(idl_type)
            if strcmp(format_char, 'f') || strcmp(format_char, 'd') then format_char = 'e'

            ;create a format code to print the values
            n = string(n_values, format='(i0)')
            format_code = n + '(2x, ' + format_char + '0)'

            ;Print the information.
            print, (*self.variables).(iVar).attributes.(iAtt).name, $
                   (*self.variables).(iVar).attributes.(iAtt).cdftype, $
                   (*self.variables).(iVar).attributes.(iAtt).value, $
                   format='(1x, a-23, 2x, a-15, ' + format_code + ')'
        endfor
    endfor
end


;+
; The purpose of this method is to print the variable names within the CDf file
; along with their variable number, z-variable status, dimensions, and max record.
;
; :Private:
;
; :Pre:
;       Must call load_variables method first.
;
; :History:
;   Modification History::
;       03/05/2013  -   Variables with multiple dimensions now displayed properly. 
;                           Width of displayed text fits width of variable names. - MRA
;-
pro CDF_Info::Help_Variables, variable, $
ZVARIABLE=zvariable
    compile_opt idl2, hidden
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

;---------------------------------------------------------------------
;Check Inputs ////////////////////////////////////////////////////////
;---------------------------------------------------------------------
    if n_elements(zvariable) eq 0 then zvariable = 1
    
    ;If VARIABLE was given, Ensure they are variable numbers
    if n_elements(variable) eq 0 $
        then varnum = self -> Get_VarNum(IS_ZVAR=zvariable) $
        else varnum = self -> Ensure_VarType(variable, /NUMBER, IS_ZVAR=zvariable)
        
    ;Properly size the width of the output
    tags = tag_names(*self.variables)
    max_width = max(strlen(tags))
    if max_width-13 lt 0 $
        then header_name_width = '1' $
        else header_name_width = string(max_width - 13, format='(i0)')
    
    
    ;Print an output header
    print, 'VARIABLE NAME', '-#-', '-Z-', '-TYPE-', '-MAXREC-', '--DIM--', $
           format='(' + header_name_width + 'x, a13, 3x, a3, 2x, a3, 2x, a6, 12x, a8, 2x, a7)'

;---------------------------------------------------------------------
;Print Variable Information //////////////////////////////////////////
;---------------------------------------------------------------------
    ;Start printing their variable number, name, and z-variable status
    n_vars = n_elements(varnum)
    for i = 0, n_vars-1 do begin
        ;z-variables are stored after the r-variables, so if ZVARIABLE=1, then add
        ;NRVARS to VARNUM number to get the z-variable structure index
        varindex = varnum[i] + self.nrvars*zvariable[i]
        
        ndims = string(n_elements((*self.variables).(varindex).dim), format='(i0)')
        print, (*self.variables).(varindex).name, $
               (*self.variables).(varindex).number, $
               (*self.variables).(varindex).is_zvar, $
               (*self.variables).(varindex).datatype, $
               (*self.variables).(varindex).maxrec, $
               (*self.variables).(varindex).dim, $
               format='(a' + string(max_width, format='(i0)') + $   ;varname
                      ', 3x, i3, 3x, i1.1, 3x, a-17, ' + $          ;varnum, is_zvar, vartype
                      'i8, 3x, ' + ndims + '(i0, 1x))'              ;maxrec, dims
    endfor
end


;+
; The purpose of this method is to print the attributes of the CDF file along with
; their attribute scope (global or variable).
;
; :Private:
;-
pro CDF_Info::Help_Attributes, attribute
    compile_opt idl2, hidden
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif
    
    ;If ATTRIBUTE was given, ensure that it is a number. Otherwise, get all att numbers
    if n_elements(attribute) eq 0 $
        then attnum = self -> Get_AttNum() $
        else attnum = self -> Ensure_AttType(attribute, /NUMBER)
    
    ;Print a header
    print, '-Att #-', '-Att Scope-', '-Attribute Name-', $
           '-maxR-', '-maxZ-', '-nG-', '-nR-', '-nZ-', $
           format='(a7, 2x, a11, 9x, a16, 20x, 2(2x, a7) 4(2x, a4))'

    ;Step through all of the attributes
    natts = n_elements(attnum)
    for i = 0, natts-1 do begin
        print, (*self.attributes).(i).number, $
               (*self.attributes).(i).scope, $
               (*self.attributes).(i).name, $
               (*self.attributes).(i).maxrentry, $
               (*self.attributes).(i).maxzentry, $
               (*self.attributes).(i).numgentries, $
               (*self.attributes).(i).numrentries, $
               (*self.attributes).(i).numzentries, $
               format='(1x, i3, 6x, a-20, a-39, i3, 6x, i3, 4x, 2(i3, 3x), i3)'
    endfor
end


;+
; The purpose of this method is to convert a CDF variable number to a CDF variable
; name.
;
; :Params:
;
;       VARNUM:             in, optional, type=int/intarr
;                           CDF variable number. If not given, all of the variable names
;                               will be returned.
;
; :Keywords:
;       ZVARIABLE:          in, optional, type=Boolean, default=0
;                           Indicate that `VARNUM` is a zvariable. If `VARNUM` is an array
;                               and ZVARIABLE is a scalar, then the same zvariable state
;                               is applied to all elements of `VARNUM`. Otherwise,
;                               ZVARIABLE must have the same number of elements as `VARNUM`
;
; :Returns:
;       VNAME:              The variable name associated with VARNUM
;-
function CDF_Info::Get_VarName, varnum, $
ZVARIABLE=zvariable
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif
    
    ;Default to Z-Variable
    if n_elements(zvariable) eq 0 then zvariable = 1

    ;Make sure VARNUM is a number.
    if n_params() eq 1 && MrIsA(varnum, /NUMBER) eq 0 then $
        message, 'VARNUM must be a number identifying the variable ID.'
    
    ;Get all of the variable names, numbers, and z-variable status
    vname = strarr(self.nvars)
    vnums = intarr(self.nvars)
    is_zvar = bytarr(self.nvars)
    for i = 0, self.nvars-1 do begin
        vname[i] = (*self.variables).(i).name
        vnums[i] = (*self.variables).(i).number
        is_zvar[i] = (*self.variables).(i).is_zvar
    endfor
    
    ;If no parameters were given, return all of the names
    if n_params() eq 0 then return, vname
    
    ;Number the z-variables after the r-variables
    in_zvarnum = varnum + zvariable*self.nrvars
    all_zvnums = vnums + is_zvar*self.nrvars

    ;Which numbers were given?
    void = isMember(all_zvnums, in_zvarnum, $
                    A_INDICES=iNames, $
                    N_MATCHES=nNames, $
                    NONMEMBER_INDS=iInvalid, $
                    N_NONMEMBERS=nInvalid)

    ;Report bad numbers
    if nInvalid gt 0 then begin
        message, 'VARNUM [' + strjoin(varnum[iInvalid], ',') + $
                 '] are not valid variable numbers.', /INFORMATIONAL
    endif
    
    ;If no matches were found, return
    if nNames eq 0 then return, !Null
    
    vnames = vname[iNames]
    return, vnames
end


;+
; The purpose of this method is ot convert a CDF variable name to a CDF variable number.
;
; :Params:
;       VNAME:              in, optional, type=string/strarr
;                           CDF variable name(s). If not given, all variable numbers will
;                               be returned.
;
; :Keywords:
;       IS_ZVAR:            out, optional, type=Boolean
;                           Is the input variable a z-variable?::
;                              0 - the variable is a regular variable
;                              1 - the variable is a z-variable
;
; :Returns:
;       VARNUM:             The variable number associated with VARNAME
;
; :Uses:
;   Uses the following external programs::
;       ismember.pro
;
;-
function CDF_Info::Get_VarNum, vname, $
FOLD_CASE = fold_case, $
IS_ZVAR = is_zvar
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif
    
    ;Fold case when comparing variable names?
    if n_elements(fold_case) eq 0 then fold_case = 1
    
    ;Make sure VARNUM is a number.
    if n_params() eq 1 && MrIsA(vname, 'STRING') eq 0 then $
        message, 'VNAME must be a valid variable name.'

    ;Get all of the variable names, numbers, and z-variable status
    all_vname = strarr(self.nvars)
    all_vnums = intarr(self.nvars)
    is_zvar = bytarr(self.nvars)
    for i = 0, self.nvars-1 do begin
        all_vname[i] = (*self.variables).(i).name
        all_vnums[i] = (*self.variables).(i).number
        is_zvar[i] = (*self.variables).(i).is_zvar
    endfor
    
    ;If no parameters were given, return all of the names
    if n_params() eq 0 then return, all_vnums

    ;Which numbers were given?
    void = isMember(all_vname, vname, $
                    A_INDICES=iNames, $
                    N_MATCHES=nNames, $
                    NONMEMBER_INDS=iInvalid, $
                    N_NONMEMBERS = nInvalid, $
                    FOLD_CASE=fold_case)
    
    ;Report bad numbers
    if nInvalid gt 0 then begin
        message, 'VARNUM [' + strjoin(vname[iInvalid], ',') + $
                 '] are not valid variable numbers.', /INFORMATIONAL
    endif
    
    ;If no matches were found, return
    if nNames eq 0 then return, !Null

    is_zvar = is_zvar[iNames]
    vnums = all_vnums[iNames]
    return, vnums
end


;+
; The purpose of this method is to convert a CDF attribute number to a CDF attribute
; name.
;
; :Params:
;
;       ATTNUM:             in, required, type=int/intarr
;                           CDF global or variable attribute numbers
;
; :Returns:
;       ATTNAME:            The attribute name associated with `ATTNUM`.
;-
function CDF_Info::Get_AttName, attnum
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif

    ;Make sure VARNUM is a number.
    if n_params() eq 1 && MrIsA(attnum, /NUMBER) eq 0 then $
        message, 'ATTNUM must be a number identifying the variable ID.'
    
    ;Get all of the attribute names and numbers
    all_attnames = strarr(self.natts)
    all_attnums = intarr(self.natts)
    for i = 0, self.natts-1 do begin
        all_attnames[i] = (*self.attributes).(i).name
        all_attnums[i] = (*self.attributes).(i).number
    endfor
    
    ;If no parameters were given, return all of the names
    if n_params() eq 0 then return, all_attnames

    ;Which numbers were given?
    void = isMember(all_attnums, attnum, $
                    A_INDICES=iNames, $
                    N_MATCHES=nNames, $
                    NONMEMBER_INDS=iInvalid, $
                    N_NONMEMBERS=nInvalid)

    ;Report bad numbers
    if nInvalid gt 0 then begin
        message, 'ATTNUM [' + strjoin(all_attnums[iInvalid], ',') + $
                 '] are not valid attribute numbers.', /INFORMATIONAL
    endif
    
    ;If no matches were found, return
    if nNames eq 0 then return, !Null
    
    attnames = all_attnames[iNames]
    return, attnames
end


;+
; The purpose of this method is to convert a CDF attribute name to a CDF attribute
; number.
;
; :Params:
;       ATTNAME         -   in, required, type=string/strarr
;                           CDF global or variable attribute name for which the attribute
;                               number is desired.
;
; :Returns:
;       ATTNUM          -   The attribute number associated with `ATTNAME`.
;
; :Uses:
;   Uses the following external programs::
;       ismember.pro
;
;-
function CDF_Info::Get_AttNum, attname
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif

    ;Make sure VARNUM is a number.
    if n_params() eq 1 && MrIsA(attname, 'STRING') eq 0 then $
        message, 'ATTNAME must be a valid attribute name.'
    
    ;Get all of the attribute names and numbers
    all_attnames = strarr(self.natts)
    all_attnums = intarr(self.natts)

    for i = 0, self.natts-1 do begin
        all_attnames[i] = (*self.attributes).(i).name
        all_attnums[i] = (*self.attributes).(i).number
    endfor
    
    ;If no parameters were given, return all of the names
    if n_params() eq 0 then return, all_attnums

    ;Which numbers were given?
    void = isMember(all_attnames, attname, $
                    A_INDICES=iNames, $
                    N_MATCHES=nNames, $
                    NONMEMBER_INDS=iInvalid, $
                    N_NONMEMBERS=nInvalid)

    ;Report bad numbers
    if nInvalid then begin
        message, 'ATTNUM [' + strjoin(all_attnames[iInvalid], ',') + $
                 '] are not valid attribute numbers.', /INFORMATIONAL
    endif
    
    ;If no matches were found, return
    if nNames eq 0 then return, !Null
    
    attnums = all_attnums[iNames]
    return, attnums
end


;+
; The purpose of this method is to return properties of the CDF object
;
; :Keywords:
;       FILENAME:           out, optional, type=string
;                           The name of the CDF file being accessed
;       CDF_ID:             out, optional, type=int
;                           Data that has been read in by the READ method
;       ATTRIBUTES:         out, optional, type=structure
;                           Global and Variable attributes meta-data of the CDF file
;       VARIABLES:          out, optional, type=strarr
;                           Variable meta-data of the CDF file
;       NVARS:              out, optional, type=int
;                           Number of (r and z) variables in the CDF file
;       NRVARS:             out, optional, type=int
;                           Number of regular variables in the CDF file
;       NZVARS:             out, optional, type=int
;                           Number of z-variables in the CDF file
;       NATTS:              out, optional, type=int
;                           Number of (global and variable) attributes in the CDF file
;       NGATTS:             out, optional, type=int
;                           Number of global attributes in the CDF file
;       NVATTS:             out, optional, type=int
;                           Number of variable attributes in the CDF file
;-
pro CDF_Info::GetProperty, $
FILENAME = filename, $
CDF_ID = cdf_id, $
ATTRIBUTES = attributes, $
VARIABLES = variables, $
NVARS = nvars, $
NRVARS = nrvars, $
NZVARS = nzvars, $
NATTS = natts, $
NGATTS = ngatts, $
NVATTS = nvatts
    compile_opt idl2
    on_error, 2
    
    ;Return properties of the object.
    if arg_present(filename)   then filename = self.filename
    if arg_present(cdf_id)     then cdf_id = self.cdf_id
    if arg_present(natts)      then natts = self.natts
    if arg_present(ngatts)     then ngatts = self.ngatts
    if arg_present(nvatts)     then nvatts = self.nvatts
    if arg_present(nvars)      then nvars = self.nvars
    if arg_present(nrvars)     then nrvars = self.nrvars
    if arg_present(nzvars)     then nzvars = self.nzvars
    if arg_present(attributes) then attributes = *self.attributes
    if arg_present(variables)  then variables = *self.variables
end


;+
; The purpose of this method is to get the value of a global attribute.
;
; :Params:
;       ATTRIBUTE:          in, required, type=string/strarr
;                           The name of the global attribute for which the value is
;                               desired.
;
; :Returns:
;       ATTVALUE:           The value of `ATTRIBUTE` assiciated with `VARIABLE`.
;-
function CDF_Info::Get_GAtt, attribute
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif
    
    ;Make sure ATTRIBUTE is a number.
    attnum = self -> Ensure_AttType(attribute, /NUMBER)
    
    ;Check to see if the attribute(s) is GLOBAL in scope.
    if attnum gt self.ngatts then message, 'Attribute is not have scope GLOBAL'
    
    ;Get the attribute value(s)
    attvalue = (*self.attributes).(attnum).value
    
    return, attvalue
end


;+
;   The purpose of this method is to get the value of a variable attribute.
;
;   NOTE:
;       The search for ATTRIBUTE is case sensitive.
;
; :Params:
;       VARIABLE:           in, required, type=string/strarr/int/intarr
;                           The variable assiciated with `ATTRIBUTE`.
;       ATTRIBUTE:          in, required, type=string/strarr
;                           The name of the variable attribute for which the value is
;                               desired.
;
; :Keywords:
;       ZVARIABLE:          in, optional, type=Boolean, default=0
;                           If `VARIABLE` is a variable number, then this indicates that 
;                               `VARIABLE` is a zvariable. If `VARIABLE` is an array
;                               and ZVARIABLE is a scalar, then the same zvariable
;                               state is applied to all elements of `VARIABLE`. Otherwise,
;                               ZVARIABLE must have the same number of elements as `VARIABLE`
;
; :Returns:
;       ATTVALUE:           The value of `ATTRIBUTE` assiciated with `VARIABLE`. If
;                               `ATTRIBUTE` is not one of `VARIABLE`s attributes, then
;                               !NULL is returned.
;-
function CDF_Info::Get_VarAtt, variable, attribute, $
ZVARIABLE = zvariable
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, !Null
    endif
    
    ;Default to z-variables
    if n_elements(zvariable) eq 0 then zvariable = 1

    ;This function only works for scalars so far.
    if n_elements(variable) gt 1 || n_elements(attribute) gt 1 $
        then message, 'VARIABLES and ATTRIBUTES must both be scalars.'
    
    ;Make sure VARIABLE is a variable name and ATTRIBUTE is an attribute number
    varnum = self -> Ensure_VarType(variable, IS_ZVAR=zvariable, /NUMBER)
    attnum = self -> Ensure_AttType(attribute, /NUMBER)
    attname = self -> Get_AttName(attnum)

    ;Make sure ATTRIBUTE is not a global attribute
    if (*self.attributes).(attnum).scope ne 'VARIABLE_SCOPE' $
        then message, 'ATTRIBUTE is not VARIABLE in scope.'
    
    ;Find the variable attribute index (not the attribute index)
    iVar = varnum + zvariable*self.nrvars
    for iVAtt = 0, (*self.variables).(iVar).natts - 1 do $
        if attname eq (*self.variables).(iVar).attributes.(iVAtt).name $
            then value = (*self.variables).(iVar).attributes.(iVAtt).value
    
    if n_elements(value) eq 0 $
        then return, !Null $
        else return, value
end


;+
; The purpose of this program is to get meta-data related to a particular variable.
;-
pro CDF_Info::Get_VarInfo, variable, $
VARNUM = varnum, $
VARNAME = varname, $
IS_ZVAR = is_zvar, $
DATATYPE = datatype, $
NUMELEM = numelem, $
RECVAR = recvar, $
VARDIM = dimvar, $
DIM = dim, $
EXTENDRECS = extendrecs, $
MAXALLOCREC = maxallocrec, $
MAXREC = maxrec, $
MAX_RECS = maxrecs, $
NINDEXENTRIES = nindexentries, $
NINDEXRECORDS = nindexrecords, $
PADVALUE = padvalue, $
ZVARIABLE = zvariable
    compile_opt idl2
    on_error, 2

;---------------------------------------------------------------------
;Check Inputs \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    ;Make sure the given variable was a number. If not, get the variable number
    vnum = self -> Ensure_VarType(variable, IS_ZVAR=zvariable, /NUMBER)
        
    ;Since z-values are stored after r-values, add NRVARS to any z-variable number
    iVar = vnum + zvariable*self.nrvars
    
    ;Get the requested variable meta-data
    if arg_present(varname)       then varname       = (*self.variables).(iVar).name
    if arg_present(varnum)        then varnum        = (*self.variables).(iVar).number
    if arg_present(is_zvar)       then is_zvar       = (*self.variables).(iVar).is_zvar
    if arg_present(datatype)      then datatype      = (*self.variables).(iVar).datatype
    if arg_present(numelem)       then numelem       = (*self.variables).(iVar).numelem
    if arg_present(recvar)        then recvar        = (*self.variables).(iVar).recvar
    if arg_present(dimvar)        then dimvar        = (*self.variables).(iVar).dimvar
    if arg_present(dim)           then dim           = (*self.variables).(iVar).dim
    if arg_present(extendrecs)    then extendrecs    = (*self.variables).(iVar).extendrecs
    if arg_present(maxallocrec)   then maxallocrec   = (*self.variables).(iVar).maxallocrec
    if arg_present(maxrec)        then maxrec        = (*self.variables).(iVar).maxrec
    if arg_present(maxrecs)       then maxrecs       = (*self.variables).(iVar).maxrecs
    if arg_present(nindexentries) then nindexentries = (*self.variables).(iVar).nindexentries
    if arg_present(nindexrecords) then nindexrecords = (*self.variables).(iVar).nindexrecords
    if arg_present(padvalue)      then padvalue      = (*self.variables).(iVar).padvalue
end


;+
; The purpose of this method is to check if an attribute is contained within the
; CDF file.
;
; :Params:
;       ATTRIBUTE:          in, required, type=string/strarr
;                           CDF attribute name(s) or number(s).
;
; :Keywords:
;       FOLD_CASE:          in, optional, type=Boolean, default=1
;                           Make the search for `ATTRIBUTE` case insensitive.
;
; :Returns:
;       TF_HASATT:          1 if the CDF file has the attribute, 0 if not.
;
; :Uses:
;   Uses the following external programs::
;       ismember.pro
;-
function CDF_Info::AttExists, attribute, $
FOLD_CASE = fold_case
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif
    
    ;Default to case-folding
    if n_elements(fold_case) eq 0 then fold_case = 1
    
    ;If ATTRIBUTE is a number and it is less than the total number of attributes, then
    ;ATTRIBUTE is present. Otherwise, it is not
    if size(attribute, /TNAME) eq 'STRING' then begin
        attnames = self -> Get_AttName()
        tf_hasAtt = ismember(attnames, attribute, FOLD_CASE=fold_case)
    endif else begin
        tf_hasAtt = attribute lt self.natts
    endelse
    
    return, tf_hasAtt
end


;+
; The purpose of this method is to check if a variable is contained within the
; CDF file.
;
; :Params:
;       VARIABLE:           in, required, type=string/strarr
;                           CDF variable name(s) or number(s).
;
; :Keywords:
;       FOLD_CASE:          in, optional, type=Boolean, default=1
;                           Make the search for `VARIABLE` case insensitive.
;
; :Returns:
;       TF_HASVAR:          1 if the CDF file has the variable, 0 if not.
;
; :Uses:
;   Uses the following external programs::
;       ismember.pro
;-
function CDF_Info::VarExists, variable, $
FOLD_CASE = fold_case, $
ZVARIABLE = zvariable
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif
    
    ;Default to case-folding
    if n_elements(fold_case) eq 0 then fold_case = 1
    if n_elements(zvariable) eq 0 then zvariable = 1
    
    ;If VARIABLE is a name, check for it.
    if size(variable, /TNAME) eq 'STRING' then begin
        varnames = self -> Get_VarName()
        tf_hasVar = isMember(varnames, variable, FOLD_CASE=fold_case)
        
    ;If VARIABLE is a number and it is less than the total number of variable, then
    ;VARIABLE is present. Otherwise, it is not
    endif else begin
        tf_hasVar = variable + self.nrvars*zvariable lt self.nvars
    endelse
    
    return, tf_hasVar
end


;+
;   The purpose of this method is to check if a variable has a particular attribute.
;
;   NOTE:
;       The search for ATTRIBUTE is case sensitive.
;
; :Params:
;       ATTRIBUTE:          in, required, type=string
;                           The name of the variable attribute to check for.
;       VARIABLE:           in, required, type=string/int
;                           The variable assiciated with `ATTRIBUTE`.
;
; :Keywords:
;       ATT_VALUE:          out, optional, type=any
;                           A named variable into which the value of `ATTRIBUTE` will be
;                               returned. This only works if `TF_HAS` returns true.
;       ZVARIABLE:          in, optional, type=Boolean, default=0
;                           If `VARIABLE` is a variable number, then this indicates that 
;                               `VARIABLE` is a zvariable. If `VARIABLE` is an array
;                               and ZVARIABLE is a scalar, then the same zvariable
;                               state is applied to all elements of `VARIABLE`. Otherwise,
;                               ZVARIABLE must have the same number of elements as `VARIABLE`
;
; :Returns:
;       TF_HAS:             Returns true (1) if the `VARIABLE` has `ATTRIBUTE` and
;                               false (0) if it does not.
;-
function CDF_Info::VarHasAtt, variable, attribute, $
ATT_VALUE = att_value, $
ZVARIABLE = zvariable
    compile_opt idl2, hidden

    ;catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, -1
    endif
    
    ;Determine, first, if the attribute is contained within the file
    tf_has = self -> AttExists(attribute)

    ;If it is contained within the file...
    if tf_has then begin
        ;Try to get the variable attribute value
        att_value = self -> Get_VarAtt(variable, attribute, ZVARIABLE=zvariable)
    
        ;If !Null was returned, then the attribute does not exist for the variable.
        if n_elements(att_value) eq 0 $
            then tf_has = 0 $
            else tf_has = 1
    endif
    
    return, tf_has
end



;+
; Ensure that `ATTRIBUTE` is a name (number). If it is a number (name) instead, then
; convert it to a name (number).
;
; :Private:
;
; :Params:
;       ATTRIBUTE:          in, optional, type=string/strarr
;                           CDF attribute name(s) or number(s).
;
; :Keywords:
;       FOLD_CASE:          in, optional, type=Boolean, default=1
;                           Make the search for `ATTRIBUTE` case insensitive.
;       NAME:               in, optional, type=Boolean, default=0
;                           Ensure that `ATTRIBUTE` is a name. If not, convert it to a
;                               name. Either `NAME` or `NUMBER` must be set (but not both).
;       NUMBER:             in, optional, type=Boolean, default=0
;                           Ensure that `ATTRIBUTE` is a number. If not, convert it to a
;                               number Either `NAME` or `NUMBER` must be set (but not
;                               both).
;
; :Returns:
;       ATTRIBUTE_OUT:      The attribute names or numbers corresponding to `ATTRIBUTE` as
;                               specified by the `NAME` and `NUMBER` keywords.
;-
function CDF_Info::Ensure_AttType, attribute, $
FOLD_CASE = fold_case, $
NAME = name, $
NUMBER = number
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        if keyword_set(name) $
            then return, '' $
            else return, -1
    endif
    
    name = keyword_set(name)
    number = keyword_set(number)
    if n_elements(fold_case) eq 0 then fold_case = 1
    
    ;Make sure exactly one of NAME or NUMBER are set.
    if name + number ne 1 $
        then message, 'Exactly one of the keywords NAME and NUMBER must be set.'
    
    ;Get the variable type
    att_type = size(attribute, /type)

;---------------------------------------------------------------------
;Attribute Number ////////////////////////////////////////////////////
;---------------------------------------------------------------------
    if keyword_set(number) then begin
        if att_type eq 7 then begin
            ;Convert the attribute name to a number
            attribute_out = self -> Get_AttNum(attribute)
        
        endif else begin
            ;Make sure the attribute exists
            hasAtt = self -> AttExists(attribute)
            if hasAtt eq 0 then $
                message, 'ATTRIBUTE ' + strtrim(attribute, 2) + ' is not an available attribute.'
                
            ;Return the number that was given
            attribute_out = attribute
        endelse

;---------------------------------------------------------------------
;Attribute Name //////////////////////////////////////////////////////
;---------------------------------------------------------------------
    endif else if keyword_set(name) then begin
        if att_type eq 7 then begin
            ;Make sure the attribute exists
            hasAtt = self -> AttExists(attribute, FOLD_CASE=fold_case)
            if hasAtt eq 0 then $
                message, 'ATTRIBUTE ' + attribute + ' is not an available attribute.'
                
            ;Return the name that was given
            attribute_out = attribute
            
        ;Convert a number to a string.
        endif else attribute_out = self -> Get_AttName(attribute)
    endif
    
    return, attribute_out
end


;+
; Ensure that `VARIABLE` is a name (number). If it is a number (name) instead, then
; convert it to a name (number).
;
; :Private:
;
; :Params:
;       VARIABLE:           in, optional, type=string/strarr
;                           CDF variable name(s) or number(s).
;
; :Keywords:
;       FOLD_CASE:          in, optional, type=Boolean, default=1
;                           Make the search for `VARIABLE` case insensitive.
;       IS_ZVAR:            in, out, optional, type=Boolean, default=0
;                           Specifies that the `VARIABLE` is a z-variable. It is an input
;                               if a name was given, but a number is wanted. It is an
;                               out put if a number was given, but a name is wanted.
;                           If `VARIABLE` is an array and ZVARIABLE is a scalar, then the
;                              same zvariable state is applied to all elements of 
;                               `VARIABLE`. Otherwise, ZVARIABLE must have the same number
;                               of elements as `VARIABLE`.
;       NAME:               in, optional, type=Boolean, default=0
;                           Ensure that `VARIABLE` is a name. If not, convert it to a name.
;                               Either `NAME` or `NUMBER` must be set (but not both).
;       NUMBER:             in, optional, type=Boolean, default=0
;                           Ensure that `VARIABLE` is a number. If not, convert it to a number.
;                               Either `NAME` or `NUMBER` must be set (but not both).
;
; :Returns:
;       VARIABLE_OUT:       The variable names or numbers corresponding to `VARIABLE` as
;                               specified by the `NAME` and `NUMBER` keywords.
;-
function CDF_Info::Ensure_VarType, variable, $
FOLD_CASE = fold_case, $
IS_ZVAR = is_zvar, $
NAME = name, $
NUMBER = number
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        if keyword_set(name) $
            then return, '' $
            else return, -1
    endif
    
    name = keyword_set(name)
    number = keyword_set(number)
    if n_elements(fold_case) eq 0 then fold_case = 1
    
    ;Make sure exactly one of NAME or NUMBER are set.
    if name + number ne 1 $
        then message, 'One (and only one) of the keywords NAME and NUMBER must be set.'
    
    ;Get the variable type
    var_type = size(variable, /type)

;---------------------------------------------------------------------
;Variable Number /////////////////////////////////////////////////////
;---------------------------------------------------------------------
    if keyword_set(number) then begin
        if var_type eq 7 then begin
            ;Convert a number to a name
            variable_out = self -> Get_VarNum(variable, IS_ZVAR=is_zvar)
            
        endif else begin
            ;Check for existence
            hasVar = self -> VarExists(variable, ZVARIABLE=is_zvar)
            if hasVar eq 0 then $
                message, 'Variable "' + strtrim(variable, 2) + '" is not a valid variable number.'
            
            ;Keep the number that was given
            variable_out = variable
        endelse
            
;---------------------------------------------------------------------
;Variable Name ///////////////////////////////////////////////////////
;---------------------------------------------------------------------
    endif else if keyword_set(name) then begin
        if var_type eq 7 then begin
            ;Check for existence
            hasVar = self -> VarExists(variable, FOLD_CASE=fold_case)
            if hasVar eq 0 then $
                message, 'Variable "' + variable + '" is not an available variable name.'
        
            ;Keep the name that was given.
            variable_out = variable
            
        ;Convert a number to a name
        endif else variable_out = self -> Get_VarName(variable, ZVARIABLE=is_zvar)
    endif
    
    return, variable_out
end


;+
; The purpose of this method is to load the Global and Variable attribute meta-data
; from within a CDF file.
;
; To access attribute properties::
;
;       *self.attributes.[tag].name
;                             .number
;                             .value
;                             .scope
;                             .maxgentry
;                             .maxrentry
;                             .maxzentry
;                             .numgentries
;                             .numrentries
;                             .numzentries
;
; where [tag] is the attribut name, converted to an IDL valid name when necessary.
;
; :Private:
;-
pro CDF_Info::Load_Attributes
    compile_opt idl2, hidden
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif
    
    nGatts = 0
    nVatts = 0
    
    ;Set through all of the attributes and get their properties.
    for attnum = 0, self.natts - 1 do begin
        ;inquire about the attribute to get its name and scope
        cdf_attinq, self.cdf_id, attnum, name, scope, rentry, zentry
        cdf_control, self.cdf_id, ATTRIBUTE=attnum, GET_ATTR_INFO=att_info

        ;Get each global attribute value
        for gEntryNum = 0, att_info.numgentries-1 do begin
            ;Make sure the gEntry exists
            if cdf_attexists(self.cdf_id, name, gEntryNum) eq 0 then continue
            
            ;Get the gEntry value
            cdf_attget, self.cdf_id, attnum, gEntryNum, value, CDF_TYPE=cdf_type
            if gEntryNum eq 0 then gValue = make_array(n_elements(value), att_info.numgentries, TYPE=size(value, /TYPE))
            gValue[gEntryNum] = value
        endfor
        
        ;Count the number of Global and Variable Attributes
        if strpos(scope, 'GLOBAL') ne -1 then nGatts += 1
        if strpos(scope, 'VARIABLE') ne -1 then nVatts += 1
        
        ;Make a data structure of the attribute information
        att_struct = {name: name, $
                      number: attnum, $
                      scope: scope, $
                      maxrentry: rentry, $
                      maxzentry: zentry, $
                      numgentries: att_info.numgentries, $
                      numrentries: att_info.numrentries, $
                      numzentries: att_info.numzentries}
        
        ;Do not add global values if they do not exist.
        if att_info.numgentries gt 0 then att_struct = create_struct(att_struct, 'value', gValue)
        
        ;Keep all attributes under one structure.
        tag_name = idl_validname(name, /CONVERT_ALL)
        if n_elements(attributes) eq 0 $
            then attributes = create_struct(tag_name, att_struct) $
            else attributes = create_struct(attributes, tag_name, att_struct)
    endfor
    
    ;Save the attribute structure as an object property
    self.ngatts = nGatts
    self.nvatts = nVatts
    self.attributes = ptr_new(attributes, /NO_COPY)
end



;+
; The purpose of this method is to load metadata associated with each variable in the
; CDF file.
;
; Note::
;       This is only set up for z-variables right now (/ZVARIABLE is set explicitly)
;
; Where property_name can be::
;       (*self.variables).[tag].name
;                              .number
;                              .natts
;                              .attributes.[tag].name
;                                               .number
;                                               .value
;                                               .type
;                                               .cdftype
;                              .is_zvar
;                              .datatype
;                              .numelem
;                              .recvar
;                              .dimvar
;                              .dim
;                              .extendrecs
;                              .maxallocrec
;                              .maxrec
;                              .maxrecs
;                              .nindexentries
;                              .nindexrecords
;                              .padvalue
;
; where [tag] is the name of the variable and attribute, respectively. This tag is
; converted to be and IDL valid name when necessary.
;
; :Private:
;
; :Uses:
;       Uses the following programs::
;           error_message.pro
;           has_tag.pro
;-
pro CDF_Info::Load_Variables
    compile_opt idl2, hidden

;---------------------------------------------------------------------
;Catch Errors ////////////////////////////////////////////////////////
;---------------------------------------------------------------------
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        
        ;Turn quiet off
        !quiet = 0
        void = cgErrorMsg()
        return
    endif
    
    ;Turn quiet on to get rid of the annoying warnings from CDF_ATTGET_ENTRY. E.g.,
    ;   % CDF_ATTGET_ENTRY: Warning: converting signed bytes to signed ints.
    !quiet = 1
    
;---------------------------------------------------------------------
;Step Through All of the Z-Variables /////////////////////////////////
;---------------------------------------------------------------------
    for varnum = 0, self.nzvars-1 do begin
        ;Keep track of how many attributes the variable has.
        att_count = 0
    
;---------------------------------------------------------------------
;Step Through All of the Attributes //////////////////////////////////
;---------------------------------------------------------------------
        for attnum = 0, self.natts-1 do begin
        
            ;read the variable attribute
            cdf_attget_entry, self.cdf_id, attnum, varnum, atttype, value, $
                              status, /ZVARIABLE, CDF_TYPE=cdftype, $
                              ATTRIBUTE_NAME=attname

            ;if the attribute does not exist, then short circuit to the next iteration,
            ;otherwise add is_zvar and the attribute name, scope, and value to the structure
            if status ne 1 then continue

            ;Create a structure to hold attribute information.
            attr_struct = {name: attname, $
                           number: attnum, $
                           value: value, $
                           type: atttype, $
                           cdftype: cdftype}
            
            ;Collect all of the attribute structures into one.
            tag_name = idl_validname(attname, /CONVERT_ALL)
            if att_count eq 0 $
                then attributes = create_struct(tag_name, attr_struct) $
                else attributes = create_struct(attributes, tag_name, attr_struct)
                
            att_count += 1
        endfor

        ;inquire about the variable to get its name
        varinq = cdf_varinq(self.cdf_id, varnum, /ZVARIABLE)
        cdf_control, self.cdf_id, VARIABLE=varnum, /ZVARIABLE, GET_VAR_INFO=varinfo
        
        ;check to see if the variable was given a pad value.
        if has_tag(varinfo, 'PADVALUE') then padvalue = varinfo.padvalue $
                                        else padvalue = 'None'
        
    ;---------------------------------------------------------------------
    ;Create a Structure Containing All Variable Attributes ///////////////
    ;---------------------------------------------------------------------
        ;Store the variable information
        temp_varatts = {name: varinq.name, $
                        number: varnum, $
                        
                        ;Variable Attribute information
                        natts: att_count, $
                        attributes: attributes, $
                        
                        ;meta-data from CDF_VARINQ
                        is_zvar: varinq.is_zvar, $
                        datatype: varinq.datatype, $
                        numelem: varinq.numelem, $
                        recvar: varinq.recvar, $
                        dimvar: varinq.dimvar, $
                        dim: varinq.dim, $                        
                        
                        ;meta-data from CDF_CONTROL
                        extendrecs: varinfo.extendrecs, $
                        maxallocrec: varinfo.maxallocrec, $
                        maxrec: varinfo.maxrec, $
                        maxrecs: varinfo.maxrecs, $
                        nindexentries: varinfo.nindexentries, $
                        nindexrecords: varinfo.nindexrecords, $
                        padvalue: padvalue}

        ;Collect all variables into a single structure.
        tag_name = idl_validname(varinq.name, /CONVERT_ALL)
        if varnum eq 0 then self.variables = ptr_new(create_struct(tag_name, temporary(temp_varatts))) $
                       else *self.variables = create_struct(*self.variables, tag_name, temporary(temp_varatts))
    endfor
    
    ;Turn quiet off.
    !quiet = 0
end


;+
; The purpose of this method is to load the Variable Attributes for each z-variable
; of the CDf file.
;
; NOTE::
;       This is only set up for z-variables right now (/ZVARIABLE is set explicitly)
;
; To access attribute properties::
;
;       attribute_value = (*self.varatts).vattname[variable_name]
;
; :Private:
;
; :Pre:
;       Depends heavily on attribute and variable meta-data being present. Must call 
;       the open, load_variables, and load_attributes methods first.
;-
pro CDF_Info::Load
    compile_opt idl2, hidden

    ;catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

    ;make a file inquiry to find out the number of [zr]variables    
    file_inq = cdf_inquire(self.cdf_id)    
    nvars = file_inq.nvars + file_inq.nzvars

    ;store the number of variables and attributes present
    self.natts = file_inq.natts
    self.nvars = nvars
    self.nrvars = file_inq.nvars
    self.nzvars = file_inq.nzvars
    
    ;Load attribute and variable meta-data
    self -> load_attributes
    self -> load_variables
end


;+
; Open the CDF file, get a CDF ID, and get the number of variables and
; attributes within the file
;
; :Params:
;       FILENAME:           in, out, optional, type=string
;                           The complete path to the CDF file to be accessed. If not
;                               provided, a dialog to allow the user to pick a file.
;                           If `FILENAME` is present but not defined, 1 will be returned
;                               if a file was chosen, 0 if not (i.e. the cancel button
;                               on the dialog box was pushed).
;
; :Keywords:
;       DIALOG_PARENT:      in, optional, type=integer
;                           The dialog parent of the file selection gui. Use only when
;                               `FILENAME` undefined.
;       DIRECTORY:          in, optional, type=string
;                           If FILENAME is not provided, then open the file-choosing
;                               dialog box in this directory.
;       FILE_STATUS:        out, optional, type=int
;                           Sucess status of the file being opened::
;                               0 - No file chosen (i.e. the cancel button on the dialog
;                                      box was pushed).
;                               1 - A file was chosen
;                               # - The index of the error returned by Catch
;-
pro CDF_Info::Open, filename, $
DIALOG_PARENT = dialog_parent, $
DIRECTORY = directory, $
FILE_STATUS = file_status
    compile_opt idl2
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        ;close the CDF file if it was opened.
        if self.cdf_id ne 0 then cdf_close, self.cdf_id
        self.cdf_id = 0
        file_status = the_error
        print, 'File name: ' + filename
        void = cgErrorMsg()
        return
    endif
    
    file_status = 0
    
    ;Make sure the filename was given and that the file exists.
    if n_elements(filename) eq 0 then begin
        filename = dialog_pickfile(FILTER='*.cdf', /FIX_FILTER, /READ, PATH=directory, $
                                   TITLE='Pick a CDF File to Read.', DIALOG_PARENT=dialog_parent)
        
        ;Check if the cancel button was pushed.
        if filename eq '' $
            then return $
            else file_status = 1
        
    ;If a filename was given, make sure it exists.
    endif else begin
        if file_test(filename) eq 0 $
            then message, 'File Invalid: "' + filename + '"' $
            else file_status = 1
    endelse

    ;open the file.
    self.cdf_id = cdf_open(filename)

    ;update the object fields
    self.filename = filename
    
    ;Load the metadata
    self -> Load
end


;+
; The purpose of this method is to create an instance of the CDF class by opening
; the desired CDF file and loading its metadata.
;
; :Params:
;       FILENAME:           in, optional, type=string
;                           The complete path to the CDF file to be accessed. If not
;                               provided, a dialog box will appear from which a file
;                               can be chosen.
;
; :Keywords:
;       DIALOG_PARENT:      in, optional, type=integer
;                           The dialog parent of the file selection gui. Use only when
;                               `FILENAME` undefined.
;       DIRECTORY:          in, optional, type=string
;                           If FILENAME is not provided, then open the file-choosing
;                               dialog box in this directory.
;       FILE_STATUS:        out, optional, type=int
;                           Sucess status of the file being opened::
;                               0 - No file chosen (i.e. the cancel button on the dialog
;                                      box was pushed).
;                               1 - A file was chosen
;                               # - The index of the error returned by Catch
;
; :Returns:
;       SUCCESS:            If `FILENAME` was (chosen and) opened successfully, then a
;                           valid object reference is returned (1). Otherwise, an invalid
;                           object reference is returned (0).
;-
function CDF_Info::init, filename, $
DIALOG_PARENT = dialog_parent, $
DIRECTORY = directory, $
FILE_STATUS = file_status
    compile_opt idl2
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif
    
    ;Allocate Pointers
    self.variables = ptr_new(/ALLOCATE_HEAP)
    self.attributes = ptr_new(/ALLOCATE_HEAP)

    ;Open the file and load the meta-data
    self -> open, filename, $
                  DIALOG_PARENT=dialog_parent, $
                  DIRECTORY=directory, $
                  FILE_STATUS=file_status

    if file_status eq 1 $
        then return, 1 $
        else return, 0
end


;+
; Clean up after the object is destroyed
;
; :Hidden:
;-
pro CDF_Info::cleanup
    compile_opt idl2
    
    ;close the CDF file
    self -> Close
    
    ;free the pointers (pointers within pointer arrays are recursively destroyed)
    ptr_free, self.attributes
    ptr_free, self.variables
end


;+
; The init method for CDF_Info__DEFINE.PRO
;
; :Hidden:
;
; :Fields:
;       FILENAME:           The name of the CDF file begin read.
;       CDF_ID:             The CDF ID number of the file being read.
;       NATTS:              The number of attributes within the CDF file.
;       NGATTS:             Number of Global Attributes within the CDF file.
;       NVATTS:             Number of Variable attributes within the CDF file.
;       NVARS:              Number of variables within the CDF file.
;       NRVARS:             Number of Regular variables within the CDF file.
;       NZVARS:             Number of Z-Variables within the CDF file.
;       ATTRIBUTES:         Each attribute and its meta-data.
;       VARIABLES:          Each variable and its meta-data.
;-
pro CDF_Info__define
    compile_opt idl2
    
    define = {CDF_Info, $
              Inherits IDL_Object, $
              filename: '', $
              cdf_id: 0L, $
              natts: 0L, $
              ngatts: 0L, $
              nvatts: 0L, $
              nvars: 0L, $
              nrvars: 0L, $
              nzvars: 0L, $
              attributes: ptr_new(), $
              variables: ptr_new()}
end